#include "testing.h"
#include "context.h"
#include "paging_api.h"
#include "get_pages.h"

#include "result_str.h"

#include "handlers.h"

#define info(...) printk("<1>" __VA_ARGS__)

#define N_PAGES_4KB 10
#define N_PAGES_2MB 10
#define N_PAGES_1GB 1

#define N_PAGES (N_PAGES_4KB + N_PAGES_2MB + N_PAGES_1GB)

#define N_COMMANDS 100

int max_name;

void *idt_page;
struct test_page pages[N_PAGES];
//void *temp_page;

//u32 off[N_PAGES];
//char *test[N_PAGES];
//u64 allocated_pages[N_PAGES];

struct test_command commands[N_COMMANDS], *next_comm = commands;
char *names[N_COMMANDS], **cur_name = names;



const char * rt_res_str(int rt_res) {
  switch (rt_res) {
    case RT_OK:    return "RT_OK";
    case RT_END:   return "RT_END";
    case RT_ERROR: return "RT_ERROR";
  }
  
  return "RT_?";
}

void print_test_results(int rt_res) {
  info("run_test -> %s\n", rt_res_str(rt_res));
  
  print_table ((void *)TESTER.results, (void *)TESTER.result, names, max_name);
}



int _init_contexts(void)
{
  u64 *pml4 = pages[0].virt;
  
  save_context (&(TESTER.linux_context));
  
  pml4_map_kernel(pml4);
  
  idt_page = (void *) get_zeroed_page (GFP_DMA);
  if (0 == idt_page)
    return 1;
  
	init_test_idt64(idt_page, (u64)handlers, 16);
  
  TESTER.test_context.idtr = make_test_idtr(idt_page);
  
  TESTER.test_context.cr3 = (TESTER.linux_context.cr3 & 0xfff) | virt_to_phys(pml4);
  
  return 0;
}



int _init_pages(void)
{
  int i, j;
  TESTER.pages = pages;
  TESTER.h.n_pages = N_PAGES;
  
  i=0;
  
  for (j=0; j<N_PAGES_4KB; ++j, ++i) {
    pages[i].offset_mask = OFFSET_MASK_4KB;
  }
  
  for (j=0; j<N_PAGES_2MB; ++j, ++i) {
    pages[i].offset_mask = OFFSET_MASK_2MB;
  }
  
  for (j=0; j<N_PAGES_1GB; ++j, ++i) {
    pages[i].offset_mask = OFFSET_MASK_1GB;
  }
  
  return l_get_pages (pages, N_PAGES);
}



int init_tester(void)
{
  if (_init_pages() != 0)
    return 1;
  
  if (_init_contexts() != 0)
    return 2;
  
  TESTER.results = (void *) get_zeroed_page (GFP_KERNEL);
  TESTER.result = TESTER.results;
  if (0 == TESTER.results)
    return 3;
  
  TESTER.cmd_ind = 0;
  TESTER.commands = commands;
  TESTER.h.n_commands = 0;
  
  return 0;
}



void check(u32 page) {
  if (next_comm - commands >= N_COMMANDS)
    info("ERROR: too many commands\n");
  if (page >= N_PAGES)
    info("ERROR: wrong page\n");
}



struct test_command *new_command(char *name, u32 cmd, u32 page)
{
  check(page);
  
  next_comm->cmd = cmd;
  next_comm->page = page;
  next_comm->has_result = (name && (name[0] != '@') ? 1 : 0);
  
  ++next_comm;
  
  if (name) {
    if (name[0] != '@')
      max_name = max(max_name, (int)strlen(name));
    *cur_name++ = name;
  }
  
  TESTER.h.n_commands ++;
  
  return next_comm - 1;
}

void addr(char *name, u32 cmd, u64 vaddr, u32 page)
{
  struct test_command *c = new_command (name, cmd, page);
  
  c->vaddr = vaddr;
}

void _text(char *str) {
  *cur_name++ = (str[0] == '@' ? str : "@wrong comment");
}

#define text(str) _text("@ " str)


void _map(char *name, u32 page, u32 pos, u32 page2)
{
  struct test_command *c = new_command (name, CMD_MAP, page);
  
  c->page2 = page2;
  c->pos = pos;
  c->addr_mask = ADDR_MASK_64_4KB;
}

void _map_big(char *name, u32 page, u32 pos, u32 page2) {
  struct test_command *c = new_command (name, CMD_MAP, page);
  
  c->page2 = page2;
  c->pos = pos;
  c->addr_mask = ADDR_MASK_64_2MB; // == ..._1GB
}


void _flags(char *name, u32 page, u32 pos, u64 flags)
{
  struct test_command *c = new_command (name, CMD_FLAGS, page);
  
  c->pos = pos;
  c->flags = flags;
  c->uflags = 0;
}



void _uflags(char *name, u32 page, u32 pos, u64 uflags)
{
  struct test_command *c = new_command (name, CMD_FLAGS, page);
  
  c->pos = pos;
  c->flags = 0;
  c->uflags = uflags;
}

#define map(b, c, d) _map(0, b, c, d)
#define mapt(a, b, c, d) _map("@ " a, b, c, d)

#define map_big(b, c, d) _map_big(0, b, c, d)
#define map_bigt(a, b, c, d) _map_big("@ " a, b, c, d)

#define flags(b, c, d) _flags(0, b, c, d)
#define flagst(a, b, c, d) _flags("@ " a, b, c, d)

#define uflags(b, c, d) _uflags(0, b, c, d)
#define uflagst(a, b, c, d) _uflags("@ " a, b, c, d)



void free_memory(void)
{
  free_page((u64)idt_page);
  free_page((u64)TESTER.results);
  
  l_free_pages(pages, N_PAGES);
}
